﻿@using System.Collections.Specialized
@using System.Text
@using System.Text.RegularExpressions
@using StackExchange.Exceptional
@using StackExchange.Opserver
@model Error
@{
    var error = Model;
    const string customDataErrorKey = "CustomDataFetchError";
    this.SetPageTitle("Error Info - " + error);
    this.SetMainTab(MainTab.Exceptions);
}
@section head {
    @if(error != null) 
    {
    <script>var Exception = @error.ToDetailedJson().AsHtml();</script>
    <script>
        $(function () {
            Status.Exceptions.init({ log: '@error.ApplicationName', id: '@error.GUID' });
        });
    </script>
    }
}
@functions
{
    private static HashSet<string> hiddenHttpKeys = new HashSet<string>
                                                        {
                                                            "ALL_HTTP",
                                                            "ALL_RAW",
                                                            "HTTP_CONTENT_LENGTH",
                                                            "HTTP_CONTENT_TYPE",
                                                            "HTTP_COOKIE",
                                                            "QUERY_STRING"
                                                        };

    private static HashSet<string> defaultHttpKeys = new HashSet<string>
                                                     {
                                                         "APPL_MD_PATH",
                                                         "APPL_PHYSICAL_PATH",
                                                         "GATEWAY_INTERFACE",
                                                         "HTTP_ACCEPT",
                                                         "HTTP_ACCEPT_CHARSET",
                                                         "HTTP_ACCEPT_ENCODING",
                                                         "HTTP_ACCEPT_LANGUAGE",
                                                         "HTTP_CONNECTION",
                                                         "HTTP_KEEP_ALIVE",
                                                         "HTTPS",
                                                         "INSTANCE_ID",
                                                         "INSTANCE_META_PATH",
                                                         "PATH_INFO",
                                                         "PATH_TRANSLATED",
                                                         "REMOTE_PORT",
                                                         "SCRIPT_NAME",
                                                         "SERVER_NAME",
                                                         "SERVER_PORT",
                                                         "SERVER_PORT_SECURE",
                                                         "SERVER_PROTOCOL",
                                                         "SERVER_SOFTWARE"
                                                     };
                                                     
    public IHtmlString RenderVariableTable(string title, string className, NameValueCollection vars)
    {
        if (vars == null || vars.Count == 0) return "".AsHtml();
        
        var result = new StringBuilder();
        var hiddenRows = new StringBuilder();
        
        result.AppendFormat("    <div class=\"{0}\">", className);
        result.AppendFormat("      <h3 class=\"kv-title\">{0}</h3>", title);
        result.AppendFormat("      <div class=\"side-scroll\">");
        result.AppendFormat("        <table class=\"kv-table\">");
        
        foreach (var k in vars.AllKeys.Where(key => !hiddenHttpKeys.Contains(key)).OrderBy(k => k))
        {
            // If this has no value, skip it
            if (vars[k].IsNullOrEmpty()) { continue; }
            // If this is a hidden row, buffer it up, since CSS has no clean mechanism for :visible:nth-row(odd) type styling behavior
            var hidden = defaultHttpKeys.Contains(k);
            var sb = hidden ? hiddenRows : result;
            sb.AppendFormat("          <tr{2}><td class=\"key\">{0}</td><td class=\"value\">{1}</td></tr>", k, Linkify(vars[k]), hidden ? " class=\"hidden\"" : "");
        }
        if (vars["HTTP_HOST"].HasValue() && vars["URL"].HasValue())
        {
            var ssl = vars["HTTP_X_FORWARDED_PROTO"] == "https" || vars["HTTP_X_SSL"].HasValue() || vars["HTTPS"] == "on";
            var url = string.Format("http{3}://{0}{1}{2}", vars["HTTP_HOST"], vars["URL"], vars["QUERY_STRING"].HasValue() ? "?" + vars["QUERY_STRING"] : "", ssl ? "s" : "");
            result.AppendFormat("          <tr><td class=\"key\">URL and Query</td><td class=\"value\">{0}</td></tr>", vars["REQUEST_METHOD"] == "GET" ? Linkify(url) : url.HtmlEncode());
        }
        result.Append(hiddenRows);
        result.AppendFormat("        </table>");
        result.AppendFormat("      </div>");
        result.AppendFormat("    </div>");
        return result.ToString().AsHtml();
    }


    private string Linkify(string s)
    {
        if (Regex.IsMatch(s, @"%[A-Z0-9][A-Z0-9]"))
        {
            s = Server.UrlDecode(s);
        }

        if (s != null && Regex.IsMatch(s, "^(https?|ftp|file)://"))
        {
            //@* || (Regex.IsMatch(s, "/[^ /,]+/") && !s.Contains("/LM"))*@ // block special case of "/LM/W3SVC/1"
            var sane = SanitizeUrl(s);
            if (sane == s) // only link if it's not suspicious
                return string.Format(@"<a href=""{0}"">{1}</a>", sane, Server.HtmlEncode(s));
        }

        return Server.HtmlEncode(s);
    }

    private static readonly Regex _sanitizeUrl = new Regex(@"[^-a-z0-9+&@#/%?=~_|!:,.;\*\(\)\{\}]", RegexOptions.IgnoreCase | RegexOptions.Compiled);
    public static string SanitizeUrl(string url)
    {
        return url.IsNullOrEmpty() ? url : _sanitizeUrl.Replace(url, "");
    }

}
<div id="ErrorInfo">
@if (error == null)
{
    <div class="top-section error-bread-top">
        <div class="error-header"><a href="/exceptions">Exceptions</a> > <span class="error-title">Error not found</span></div>
    </div>
    <div class="error-not-found">Error was not found.  If this link worked previously, the error was hard deleted.</div>
}
else
{
    <div class="top-section error-bread-top">
        <div class="error-header"><a href="/exceptions">Exceptions</a> > <a href="/exceptions?log=@error.ApplicationName.UrlEncode()">@error.ApplicationName</a> > <span class="error-title">@error.Message</span> <span class="similar-link">(<a href="/exceptions/similar?id=@error.GUID&app=@error.ApplicationName.UrlEncode()">view similar</a>)</span></div>
    </div>
    <div class="error-info">
        <div class="error-type">@error.Type</div>
        <pre class="error-detail">@error.Detail
        </pre>
        <p class="error-time">occurred <b title="@error.CreationDate.ToLongDateString() at @error.CreationDate.ToLongTimeString()">@error.CreationDate.ToRelativeTime()</b> on @error.MachineName@if(!error.DeletionDate.HasValue && Current.User.IsExceptionAdmin){<text> <span class="info-delete-link">(<a class="info-link" href="#" title="delete this error from the log">delete</a>)</span></text>}</p>
        @if (!string.IsNullOrEmpty(error.SQL))
        {
            <h3 class="kv-title">SQL</h3>
            <pre class="sql-detail">@error.SQL</pre>
            <br/>
        }
        @RenderVariableTable("Server Variables", "server-variables", error.ServerVariables)
        @if (error.CustomData != null && error.CustomData.Count > 0)
        {
            var errored = error.CustomData.ContainsKey(customDataErrorKey);
            var cdKeys = error.CustomData.Keys.Where(k => k != customDataErrorKey);
            <div class="custom-data">
                @if (errored)
                {
                    <h3 class="kv-title title-error">Custom - Error while gathering custom data</h3>
                } else
                {
                    <h3 class="kv-title">Custom</h3>
                }
                @if (cdKeys.Any(k => k != customDataErrorKey))
                {
                    <div class="side-scroll">
                        <table class="kv-table">
                            @foreach (var cd in cdKeys)
                            {
                                <tr>
                                    <td class="key">@cd</td>
                                    <td class="value">@Linkify(error.CustomData[cd])</td>
                                </tr>
                            }
                        </table>
                    </div>
                }
                @if(errored)
                {
                    <span class="custom-error-label">GetCustomData threw an exception:</span>
                    <pre class="error-detail">@error.CustomData[customDataErrorKey]</pre>
                }
            </div>
        }
        @RenderVariableTable("QueryString", "querystring", error.QueryString)
        @RenderVariableTable("Form", "form", error.Form)
        @RenderVariableTable("Cookies", "cookies", error.Cookies)
    </div>
}
</div>